﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using EnvDTE80;
using System.IO;

namespace SnippetCreator
{
    public partial class SnippetForm : Form
    {
        private Color _markColor = Color.FromArgb(180, 228, 180);
        private Color _highLightColor = Color.LightSkyBlue;
        private Color _textBackColor = Color.White;
        private Color _newMarkColor = Color.Yellow;

        private DTE2 _applicationObject;
        private SnippetLiteral _tempLiteral;
        private string _originalRtf;
        private bool ctrlKeyPressed = false;

        private Snippet _snippet;
        public Snippet Snippet
        {
            get
            {
                return _snippet;
            }
        }

        public SnippetForm(DTE2 applicationObject)
        {
            InitializeComponent();
            _applicationObject = applicationObject;
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            InitializeSnippetTextBox();
            InitializeSnippet();
            FillSnippetCode();
        }
        #region Initializing controls...
        private void InitializeSnippetTextBox()
        {
            snippetTextBox.BackColor = _textBackColor;
            snippetTextBox.KeyDown += delegate(object sender, KeyEventArgs e) { ctrlKeyPressed = e.Control; };
            snippetTextBox.KeyUp += delegate(object sender, KeyEventArgs e) { ctrlKeyPressed = false; };
            addLiteral.Click += delegate(object sender, EventArgs e) { addLiteral.ShowDropDown(); };
        }

        private void InitializeSnippet()
        {
            _snippet = new Snippet();
            snippetBindingSource.DataSource = _snippet.Header;
        }

        private void FillSnippetCode()
        {
            EnvDTE.Document doc = _applicationObject.ActiveDocument;
            snippetTextBox.ReadOnly = false;
            snippetTextBox.Paste();
            Clipboard.Clear();
            _originalRtf = snippetTextBox.Rtf;
            _snippet.Code = snippetTextBox.Text;
            _snippet.Language = doc.Language;
            snippetTextBox.ReadOnly = true;

        }
        #endregion

        #region Snippet TextBox toolbar commands handling...

        #region Markup literals...
        private void markLiteralButton_Click(object sender, EventArgs e)
        {
            if (snippetTextBox.SelectionLength > 0)
            {
                _tempLiteral = new SnippetLiteral();
                _tempLiteral.Positions.Add(new LiteralPosition(snippetTextBox.SelectionStart,
                    snippetTextBox.SelectionLength));
                snippetTextBox.SelectionBackColor = _newMarkColor;
                EnableLiteralCommands(true);
                snippetTextBox.DeselectAll();
            }
        }

        private void markAllLiteralsButton_Click(object sender, EventArgs e)
        {
            if (snippetTextBox.SelectionLength > 0)
            {
                _tempLiteral = new SnippetLiteral();
                string toFind = snippetTextBox.SelectedText;
                int toFindLength = snippetTextBox.SelectionLength;
                int foundIndex = 0;
                while (foundIndex != -1)
                {
                    foundIndex = snippetTextBox.Find(toFind, foundIndex, RichTextBoxFinds.MatchCase);
                    if (foundIndex != -1)
                    {
                        if (!IsSelectionReserved(foundIndex, toFindLength))
                        {
                            _tempLiteral.Positions.Add(new LiteralPosition(snippetTextBox.SelectionStart,
                                snippetTextBox.SelectionLength));
                            snippetTextBox.SelectionBackColor = _newMarkColor;
                            EnableLiteralCommands(true);
                        }
                        foundIndex += snippetTextBox.SelectionLength;
                        if (foundIndex >= snippetTextBox.Text.Length)
                        {
                            foundIndex = -1;
                        }
                    }
                }
                snippetTextBox.DeselectAll();
            }
        }
        #endregion

        #region Create new literal and add new markups to existing...
        private void createLiteral_Click(object sender, EventArgs e)
        {
            if (_tempLiteral != null)
            {
                string literalText = snippetTextBox.Text.Substring(_tempLiteral.Positions[0].Start,
                    _tempLiteral.Positions[0].Length);
                LiteralForm form = new LiteralForm(literalText, _snippet, _tempLiteral);
                if (form.ShowDialog() == DialogResult.OK)
                {
                    // Add new literal to snippet object
                    _snippet.Literals.Add(_tempLiteral);

                    // Add new literal to LiteralsList
                    ListViewItem lvi = new ListViewItem();
                    lvi.Text = _tempLiteral.ID;
                    lvi.SubItems.Add(_tempLiteral.Default);
                    lvi.SubItems.Add(_tempLiteral.ToolTip);
                    lvi.Tag = _tempLiteral;
                    literalsList.Items.Add(lvi);

                    // Add new literal to "Add to..." menu
                    ToolStripItem literalStripItem = addLiteral.DropDownItems.Add(_tempLiteral.ID);
                    literalStripItem.Name = _tempLiteral.ID;
                    literalStripItem.Tag = _tempLiteral;
                    literalStripItem.ToolTipText = _tempLiteral.ToolTip;
                    literalStripItem.Click += new EventHandler(addLiteral_Click);

                    // Remember code and manage buttons
                    SaveTextBox();
                    _tempLiteral = null;
                    EnableLiteralCommands(false);
                    ResizeLiteralsList();
                }
            }
        }

        void addLiteral_Click(object sender, EventArgs e)
        {
            ToolStripItem literalStripItem = sender as ToolStripItem;
            if (_tempLiteral != null && literalStripItem != null)
            {
                SnippetLiteral literal = literalStripItem.Tag as SnippetLiteral;
                if (literal != null)
                {
                    literal.Positions.AddRange(_tempLiteral.Positions);
                    SaveTextBox();
                    _tempLiteral = null;
                    EnableLiteralCommands(false);
                }
            }

        }
        #endregion

        private void refreshButton_Click(object sender, EventArgs e)
        {
            snippetTextBox.Rtf = _originalRtf;
            EnableLiteralCommands(false);
            _tempLiteral = null;
        }

        private void clearItem_Click(object sender, EventArgs e)
        {
            int cPos = snippetTextBox.SelectionStart;
            foreach (SnippetLiteral literal in _snippet.Literals)
            {
                int nItems = literal.Positions.Count;
                for (int i = 0; i < nItems; i++)
                {
                    LiteralPosition pos = literal.Positions[i];
                    if (cPos >= pos.Start && cPos <= pos.End)
                    {
                        snippetTextBox.Select(pos.Start, pos.Length);
                        snippetTextBox.SelectionBackColor = _textBackColor;
                        if (literal.Positions.Remove(pos))
                        {
                            nItems--;
                        }
                    }
                }
            }
            snippetTextBox.DeselectAll();
            SaveTextBox();
        }
        #endregion

        #region SnippetTextBox even handlers...
        private void snippetTextBox_SelectionChanged(object sender, EventArgs e)
        {
            if (snippetTextBox.SelectionLength > 0 && _tempLiteral == null
                && !IsSelectionReserved(snippetTextBox.SelectionStart, snippetTextBox.SelectionLength))
            {
                selAllLiteralsButton.Enabled = selectLiteralButton.Enabled = true;
            }
            else
            {
                selAllLiteralsButton.Enabled = selectLiteralButton.Enabled = false;
            }
        }

        private void snippetTextBox_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            SelectWord();
        }

        private void snippetTextBox_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && ctrlKeyPressed)
            {
                SelectWord();
            }
            clearItem.Enabled = IsSelectionReserved(snippetTextBox.SelectionStart, snippetTextBox.SelectionLength);
        }
        #endregion

        #region Handle form buttons...
        private void okButton_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(shortcutTextBox.Text))
            {
                MessageBox.Show("Please, enter shortcut for your snippet.", "Validation error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                DialogResult = DialogResult.None;
            }
            else
            {
                _snippet.Code = ParseAndClearCode(snippetTextBox.Text);
            }
        }

        private void aboutButton_Click(object sender, EventArgs e)
        {
            AboutBox form = new AboutBox();
            form.ShowDialog();
        }
        #endregion

        #region Handling literals list events...
        private void editLiteralButton_Click(object sender, EventArgs e)
        {
            if (literalsList.SelectedItems.Count > 0)
            {
                ListViewItem lvi = literalsList.SelectedItems[0];
                SnippetLiteral literal = lvi.Tag as SnippetLiteral;
                if (literal != null)
                {
                    string literalText = snippetTextBox.Text.Substring(literal.Positions[0].Start,
                        literal.Positions[0].Length);
                    LiteralForm form = new LiteralForm(literalText, _snippet, literal);
                    ToolStripItem literalStripItem = addLiteral.DropDownItems[literal.ID];
                    if (form.ShowDialog() == DialogResult.OK)
                    {
                        lvi.SubItems[0].Text = literal.ID;
                        lvi.SubItems[1].Text = literal.Default;
                        lvi.SubItems[2].Text = literal.ToolTip;
                        ResizeLiteralsList();
                        literalStripItem.Text = literal.ID;
                        literalStripItem.Name = literal.ID;
                        literalStripItem.ToolTipText = literal.ToolTip;
                    }
                }
            }
        }

        private void literalsList_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            HihlightAll(_markColor);
            if (literalsList.SelectedItems.Count > 0)
            {
                editLiteralButton.Enabled = delLiteralButton.Enabled = true;
                SnippetLiteral thisLiteral = literalsList.SelectedItems[0].Tag as SnippetLiteral;
                HighlightLiteral(thisLiteral, _highLightColor);
                resetButton.Enabled = true;
            }
        }

        private void delLiteralButton_Click(object sender, EventArgs e)
        {
            if (literalsList.SelectedItems.Count > 0)
            {
                SnippetLiteral thisLiteral = literalsList.SelectedItems[0].Tag as SnippetLiteral;
                if (thisLiteral != null)
                {
                    string key = thisLiteral.ID;
                    HighlightLiteral(thisLiteral, _textBackColor);
                    if (_snippet.Literals.Remove(thisLiteral))
                    {
                        SaveTextBox();
                        literalsList.Items.Remove(literalsList.SelectedItems[0]);
                        addLiteral.DropDownItems.RemoveByKey(key);
                    }
                }
            }
        }
        #endregion

        #region Private methods...

        #region Selecting whole word...
        private void SelectWord()
        {
            int start = 0;
            int end = 0;
            start = end = snippetTextBox.SelectionStart;
            while (start > 0)
            {
                if (IsSeparator(start))
                {
                    break;
                }
                start--;
            }
            start = (start > 0) ? start + 1 : 0;
            while (end < snippetTextBox.Text.Length)
            {
                if (IsSeparator(end))
                {
                    break;
                }
                end++;
            }
            snippetTextBox.Select(start, end - start);
        }

        private bool IsSeparator(int index)
        {
            char[] separators = new char[] { ' ', '-', '+', '=', '<', '>', ';', '!', '@', '#', '$', '%', '^', 
                '&', '*', '(', ')', ':', '?', ',', '|', '\"', '\'' };
            if (char.IsControl(snippetTextBox.Text, index) || 
                Array.Exists<char>(separators, delegate(char c) { return c == snippetTextBox.Text[index]; }))
            {
                return true;
            }
            return false;
        }
        #endregion

        #region Parsing code...
        private string ParseAndClearCode(string code)
        {
            foreach (SnippetLiteral literal in _snippet.Literals)
            {
                string literalID = String.Format(@"${0}$", literal.ID);
                foreach (LiteralPosition pos in literal.Positions)
                {
                    code = code.Remove(pos.Start, pos.Length);
                    code = code.Insert(pos.Start, literalID);
                    MovePositions(pos.Start + literalID.Length, literalID.Length - pos.Length);
                }
            }
            return ClearTrailingSpaces(code);
        }

        private string ClearTrailingSpaces(string code)
        {
            string[] lines = code.Split(new char[] { '\n' });
            int min = code.Length;
            foreach (string line in lines)
            {
                if (!String.IsNullOrEmpty(line))
                {
                    int i = 0;
                    for (i = 0; i < line.Length; i++)
                    {
                        if (line[i] != ' ')
                        {
                            break;
                        }
                    }
                    if (min >= i)
                    {
                        min = i;
                    }
                }
            }
            StringBuilder sb = new StringBuilder();
            foreach (string line in lines)
            {
                if (line.Length >= min)
                {
                    sb.AppendLine(line.Substring(min));
                }
                else
                {
                    sb.AppendLine(line);
                }
            }
            return sb.ToString();
        }

        private void MovePositions(int start, int offset)
        {
            foreach (SnippetLiteral literal in _snippet.Literals)
            {
                foreach (LiteralPosition pos in literal.Positions)
                {
                    if (pos.Start > start)
                    {
                        pos.Start += offset;
                    }
                }
            }
        }
        #endregion

        private void SaveTextBox()
        {
            foreach (SnippetLiteral literal in _snippet.Literals)
            {
                HighlightLiteral(literal, _markColor);
            }
            _originalRtf = snippetTextBox.Rtf;
        }

        private void HihlightAll(Color backColor)
        {
            foreach (SnippetLiteral literal in _snippet.Literals)
            {
                HighlightLiteral(literal, backColor);
            }
        }

        private void HighlightLiteral(SnippetLiteral thisLiteral, Color backColor)
        {
            foreach (LiteralPosition pos in thisLiteral.Positions)
            {
                snippetTextBox.Select(pos.Start, pos.Length);
                snippetTextBox.SelectionBackColor = backColor;
            }
            snippetTextBox.DeselectAll();
        }

        private bool IsSelectionReserved(int start, int length)
        {
            int end = start + length;
            foreach (SnippetLiteral literal in _snippet.Literals)
            {
                foreach (LiteralPosition pos in literal.Positions)
                {
                    if ((pos.Start <= start && pos.End >= end) ||
                        (start <= pos.Start && end >= pos.End) ||
                        (start >= pos.Start && start <= pos.End) ||
                        (end >= pos.Start && end <= pos.End))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private void ResizeLiteralsList()
        {
            literalsList.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
            literalsList.Columns[1].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
            literalsList.Columns[2].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
        }

        private void EnableLiteralCommands(bool enable)
        {
            createLiteral.Enabled = resetButton.Enabled = enable;
            if (_snippet.Literals.Count > 0)
            {
                addLiteral.Enabled = enable;
            }
        }
        #endregion

        private void titleTextBox_Enter(object sender, EventArgs e)
        {
            titleTextBox.Text = shortcutTextBox.Text;
        }


    }
}
